﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace MyWcfRestLib.Files
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    partial class FileTransfer : IFileTransfer
    {
        public static void WriteWebReponseStatus(HttpStatusCode code, string description)
        {
            var response = WebOperationContext.Current.OutgoingResponse;
            response.StatusCode = code;
            response.StatusDescription = description;
        }

        public ServiceInfo GetServiceInfoApi()
        {
            return FileServiceInfo;
        }

        public DeviceInfo[] GetDevicesApi()
        {
            DirectoryInfo dir = new DirectoryInfo(FileServiceInfo.BaseFilePath);
            DirectoryInfo[] children = dir.GetDirectories();
            DeviceInfo[] devices = null;
            if (children != null && children.Length > 0)
            {
                devices = children.Select(item => new DeviceInfo { Name = item.Name }).Where(item => item.Name != ".tmp").ToArray();
            }
            return devices;
        }

        public DeviceInfo CreateDeviceApi(Stream stream)
        {
            DeviceOpParams createDeviceParam = new DeviceOpParams(FileServiceInfo.BaseFilePath, QueryParameters, stream);

            if (!createDeviceParam.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }

            string deviceRootPath = Path.Combine(FileServiceInfo.BaseFilePath, createDeviceParam.DeviceRoot);

            if (UtilityFunctions.PathExists(deviceRootPath))
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Name is already exist!");
                return null;
            }

            try
            {
                Directory.CreateDirectory(deviceRootPath);
                return new DeviceInfo { Name = createDeviceParam.DeviceName };
            }
            catch (IOException ex)
            {
                WriteWebReponseStatus(HttpStatusCode.InternalServerError, ex.Message);
                return null;
            }
        }

        public System.IO.Stream DownloadApi(string deviceRoot, string path)
        {
            if (string.IsNullOrEmpty(deviceRoot) || string.IsNullOrEmpty(path))
            {
                WriteWebReponseStatus(HttpStatusCode.BadRequest, "");
                return null;
            }

            string filePath = Path.Combine(new string[] { FileServiceInfo.BaseFilePath, deviceRoot, path });
            if (!File.Exists(filePath))
            {
                string requestUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.AbsoluteUri;
                string message = string.Format("Request {0} not found!", requestUri);
                WriteWebReponseStatus(HttpStatusCode.NotFound, message);
                return null;
            }

            System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open);
            WebOperationContext.Current.OutgoingResponse.ContentType = ".*";
            return fs;
        }

        public void UploadApi(System.IO.Stream stream, string deviceRoot, string path)
        {
            // Upload API only use query string as parameters
            OnePathOpParams uploadParams = new OnePathOpParams(FileServiceInfo.BaseFilePath, QueryParameters);

            if (!uploadParams.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return;
            }

            string filePath = Path.Combine(new string[] { uploadParams.BasePath, uploadParams.DeviceRoot, uploadParams.WorkingPath });

            FileStream targetStream = null;

            Directory.CreateDirectory(Path.GetDirectoryName(filePath));
            using (targetStream = new FileStream(filePath, FileMode.Create,
                                  FileAccess.Write, FileShare.None))
            {
                stream.CopyTo(targetStream);
                targetStream.Close();
            }

        }

        public ChunkUploadInfo ChunkedUploadApi(System.IO.Stream stream, string uploadId, int offset)
        {
            if (null == CurrentChunkUpload)
            {
                // TODO: Create a Collection to hold all on-going chunk session.
                // Then this section would be "FindChunkSession()".
                CurrentChunkUpload = CreateNewChunkInfo();
            }

            // If input param "uploadID" is empty, means will create a new chunk uploading session.
            // Then we need clear prev session (or switch session, todo)
            if (string.IsNullOrEmpty(uploadId))
            {
                ClearIncompletedChunk(CurrentChunkUpload);
            }
            else if (uploadId != CurrentChunkUpload.UploadID)
            {
                WriteWebReponseStatus(HttpStatusCode.BadRequest, "Requested Upload ID isn't match expected!");
                return CurrentChunkUpload;
            }

            if (offset != CurrentChunkUpload.Offset)
            {
                WriteWebReponseStatus(HttpStatusCode.BadRequest, "Requested offset isn't match expected!");
                return CurrentChunkUpload;
            }

            DateTime expireTime;
            bool canParseExpireTime = DateTime.TryParse(CurrentChunkUpload.ExpireTime, out expireTime);
            if (canParseExpireTime && (DateTime.UtcNow > expireTime))
            {
                WriteWebReponseStatus(HttpStatusCode.BadRequest, "Chunk upload session expired!");
                return CurrentChunkUpload;
            }

            Debug.WriteLine(WebOperationContext.Current.IncomingRequest.ContentType);

            Directory.CreateDirectory(FileServiceInfo.TempFilePath);
            using (var targetStream = new FileStream(GetTempFileName(CurrentChunkUpload), FileMode.Append,
                                  FileAccess.Write, FileShare.None))
            {
                byte[] buffer = new byte[FileServiceInfo.MaxMessageSize];
                int byteReads = stream.Read(buffer, 0, FileServiceInfo.MaxMessageSize);
                targetStream.Write(buffer, 0, byteReads);
                CurrentChunkUpload.Offset += byteReads;
            }

            return CurrentChunkUpload;
        }

        public MetadataInfo CommitChunkedUploadApi(Stream requestStream)
        {
            ChunkUploadParams uploadParams = new ChunkUploadParams(FileServiceInfo.BaseFilePath, QueryParameters, requestStream);

            if (!uploadParams.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }

            MetadataInfo fileInfo = new MetadataInfo();
            if (CurrentChunkUpload.UploadID == uploadParams.UploadID)
            {
                CurrentChunkUpload.RootFolder = uploadParams.DeviceRoot;
                string targetFileName = Path.Combine(new string[] { uploadParams.BasePath, uploadParams.DeviceRoot, uploadParams.WorkingPath });
                string targetFolder = Path.GetDirectoryName(targetFileName);
                Directory.CreateDirectory(targetFolder);
                File.Move(GetTempFileName(CurrentChunkUpload), targetFileName);
            }
            else
            {
                WriteWebReponseStatus(HttpStatusCode.BadRequest, "Requested Upload ID isn't match expected!");
            }

            return fileInfo;
        }

        public MetadataInfo GetMetadataApi(string deviceRoot, string path)
        {
            string paramList = QueryParameters["list"];
            bool willListContent = string.IsNullOrWhiteSpace(paramList) ? false : (0 == string.Compare("true", paramList, true));

            return GetMetadata(deviceRoot, path, willListContent);
        }

        public MetadataInfo CopyFileApi(System.IO.Stream requestStream)
        {
            TwoPathOpParams copyfileParams = new TwoPathOpParams(FileServiceInfo.BaseFilePath, QueryParameters, requestStream);

            if (!copyfileParams.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }

            CopyFileOperator copier = new CopyFileOperator(copyfileParams);
            if (!copier.IsSourceExist)
            {
                WriteWebReponseStatus(HttpStatusCode.NotFound, "Source file not found!");
                return null;
            }

            if (copier.IsTargetExist)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Target file/folder is already exist!");
                return null;
            }

            // perform copying
            if (copier.PerformOperation())
            {
                return GetMetadata(copyfileParams.DeviceRoot, copyfileParams.ToPath, false);
            }
            else
            {
                return null;
            }
        }

        public MetadataInfo MoveFileApi(System.IO.Stream requestStream)
        {
            TwoPathOpParams copyfileParams = new TwoPathOpParams(FileServiceInfo.BaseFilePath, QueryParameters, requestStream);

            if (!copyfileParams.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }

            MoveFileOperator mover = new MoveFileOperator(copyfileParams);
            if (!mover.IsSourceExist)
            {
                WriteWebReponseStatus(HttpStatusCode.NotFound, "Source file not found!");
                return null;
            }

            if (mover.IsTargetExist)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Target file/folder is already exist!");
                return null;
            }

            // perform copying
            if (mover.PerformOperation())
            {
                return GetMetadata(copyfileParams.DeviceRoot, copyfileParams.ToPath, false);
            }
            else
            {
                return null;
            }

        }

        public MetadataInfo DeleteFileApi(System.IO.Stream requestStream)
        {
            OnePathOpParams theParams = new OnePathOpParams(FileServiceInfo.BaseFilePath, QueryParameters, requestStream);

            if (!theParams.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }

            DeleteFileOperator deletor = new DeleteFileOperator(theParams);
            MetadataInfo metaData = null;
            if (deletor.CanPerform)
            {
                metaData = GetMetadata(theParams.DeviceRoot, theParams.WorkingPath, false);
                deletor.PerformOperation();
            }
            else
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
            }

            return metaData;
        }

        public MetadataInfo CreateFolderApi(System.IO.Stream requestStream)
        {
            OnePathOpParams theParams = new OnePathOpParams(FileServiceInfo.BaseFilePath, QueryParameters, requestStream);

            if (!theParams.IsValid)
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }

            CreateFolderOperator creator = new CreateFolderOperator(theParams);
            if (creator.CanPerform)
            {
                if (creator.PerformOperation())
                {
                    return GetMetadata(theParams.DeviceRoot, theParams.WorkingPath, false);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                WriteWebReponseStatus(HttpStatusCode.Forbidden, "Please check parameters!");
                return null;
            }
        }
    }
}
